// sample example
//#pragma comment(linker, "/OPT:NOWIN98")

#include <list>
#include <boost/any.hpp>
#include <boost/smart_ptr.hpp>

/*
#include <boost/config.hpp>
#include <boost/pending/cstddef.hpp>
#include <boost/detail/iterator.hpp>
#include <boost/concept_check.hpp>
#include <boost/concept_archetype.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/or.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/dynamic_property_map.hpp>
*/

#include <map>
#include <iostream>
using std::cout;
using std::endl;
using std::map;
using namespace boost;
using namespace std;
// actually, you can just include specifics here
// instead of all of ogre, but this makes it easier
// on me and this example
#include <Ogre.h>
// OIS is a bit lighting, though.  just include all of it
#include <OIS/OIS.h>

// for srand()
#include <cstdlib>
// for time()
#include <ctime>

using namespace Ogre;

Root *mRoot;
RenderWindow *mWindow;
unsigned long hWnd;

Ogre::SceneNode *lightNode;

Ogre::SceneManager *sceneMgr;
Ogre::Camera *cam;
Ogre::Viewport *vp;

OIS::InputManager *input;
OIS::Keyboard *keys;
OIS::Mouse *mouse;

bool running;

void stopRenderer() {
    // the scene manager being destroyed will in turn
    // handle the camera/viewport
    mRoot->destroySceneManager(sceneMgr);

    // deleting the root will take care of the window and the root itself
    delete mRoot;
    // actually, deleting the root shoot take care of the scene manager
    // for us automatically.  but I just wanted to manually show it here.
}

void init() {

    if(mRoot->showConfigDialog())
    {
        mWindow = mRoot->initialise(true,"Ape Client");

		if(mWindow)
			mWindow->getCustomAttribute("WINDOW", &hWnd);

		//mRoot->initialise(true,"Ape Client");
        //return true;
    }
	else {
		//return false;
	}

	running = true;
}

void addResources() {
    // Load resource paths from config file
    ConfigFile cf;
    cf.load("resources.cfg");

    // Go through all sections & settings in the file
    ConfigFile::SectionIterator seci = cf.getSectionIterator();

    String secName, typeName, archName;
    while (seci.hasMoreElements())
    {
        secName = seci.peekNextKey();
		ConfigFile::SettingsMultiMap *settings = seci.getNext();
		ConfigFile::SettingsMultiMap::iterator i;
			for (i = settings->begin(); i != settings->end(); ++i)
			{

				if(secName == "Bootstrap" || secName == "General" || secName == "ET") {
					typeName = i->first;
					archName = i->second;
					ResourceGroupManager::getSingleton().addResourceLocation(archName, typeName, secName);
				}
			}
    }
	ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
	TextureManager::getSingleton().setDefaultNumMipmaps(5);
}

void initInput() {
    // aRGHh I forgot about input -_-

    // create input "manager" or "hook", as I call it
    input = OIS::InputManager::createInputSystem(hWnd);

    keys = (OIS::Keyboard*)input->createInputObject(OIS::OISKeyboard, true);
    mouse = (OIS::Mouse*)input->createInputObject(OIS::OISMouse, true);

    // tada.  I think.  rofl just kidding.  seriously.  that's it.
}

void stopInput() {
    // destroy the keyboard object, the mouse object
    // and the input "hook"
    input->destroyInputObject(keys);
    input->destroyInputObject(mouse);
    OIS::InputManager::destroyInputSystem(input);
}

void captureInput() {
    // we have to "capture" the current "state" of the input devices
    // every single frame.  (think of it as an "input update()")
    keys->capture();
    mouse->capture();
}

void handleCamera(float dt) {
    // this is actually a bad way to do a first person camera
    // however, I'm trying to keep this relatively simple.
    static float pitch = 0, yaw = 0;

    // get the current mouse state
    const OIS::MouseState &ms = mouse->getMouseState();
    // fix pitch and yaw
    pitch += -ms.Y.rel; // add the relative mouse movement (up/down for pitching)
    yaw += -ms.X.rel; // add the relative mouse movement (left/right for yawing)
    // constrain pitch to +/- 90 degrees
    if (pitch < -90) pitch = -90;
    if (pitch >  90) pitch =  90;

    // set the camera to a default orientation and then get
    // it into our own rotation
    cam->setOrientation(Ogre::Quaternion::IDENTITY);
    // yaw first, pitch second (all in world space)
    cam->yaw(Ogre::Degree(yaw));
    cam->pitch(Ogre::Degree(pitch));

    // define a local movement vector (+X == right, +Y == yp, -Z == forward)
    Ogre::Vector3 move(0, 0, 0);
    // basic WASD.  for your own entertainment, don't hardcode like me ;)
    if (keys->isKeyDown(OIS::KC_W))
        move.z += -1;
    if (keys->isKeyDown(OIS::KC_S))
        move.z += 1;
    if (keys->isKeyDown(OIS::KC_A))
        move.x += -1;
    if (keys->isKeyDown(OIS::KC_D))
        move.x += 1;
    if (keys->isKeyDown(OIS::KC_Q))
        move.y += 1;
    if (keys->isKeyDown(OIS::KC_E))
        move.y -= 1;

	// move the camera based on where it's looking at a speed
    const float CAM_SPEED = 59; // meters per second
    cam->move(cam->getOrientation() * (move * CAM_SPEED * dt));

	
}

void setupScene() {
    sceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC); // just generic for the example
    //sceneMgr->setAmbientLight(Ogre::ColourValue(1.2, 1.2, 1.2));
	sceneMgr->setAmbientLight(ColourValue(0.0, 0.0, 0.0));
    cam = sceneMgr->createCamera("cam");
    cam->setNearClipDistance(1.0);
    cam->setFarClipDistance(3000);

    // add a viewport to the window and this camera
    // (covers up the whole window)
    vp = mWindow->addViewport(cam);
    // black background
    vp->setBackgroundColour(Ogre::ColourValue(0.0, 0.0, 0.0));
    // clear every frame for us
    //vp->setClearEveryFrame(true);

    // aspect ratio, etc.
    cam->setAspectRatio(mWindow->getWidth() / float(mWindow->getHeight()));
    // 60 degree FOV
    cam->setFOVy(Ogre::Degree(60));

	cam->setPosition(-30,90,50);

	Ogre::Light *light = sceneMgr->createLight("asdas");
	light->setType(Light::LT_POINT);
	//light->setSpecularColour(0.5,0.5,0.5);
	//light->setDiffuseColour(1.4,1.4,1.4);
	
	//light->setPosition(0,0,170);
	lightNode = sceneMgr->getRootSceneNode()->createChildSceneNode("lightNode");
	lightNode->attachObject(light);
	
	BillboardSet* bbs = sceneMgr->createBillboardSet("lightbbs", 1);
	bbs->setMaterialName("Examples/Flare");
	Billboard* bb = bbs->createBillboard(0,0,0,ColourValue(1.0, 1.0, 1.0));
	
	// attach
	lightNode->attachObject(bbs);
	lightNode->setScale(0.6,0.6,0.6);


}

void fillScene() {

	//Ogre::String meshName = "athene.mesh";
	//Ogre::String meshName = "sphere.mesh";
	Ogre::String meshName = "sheep.mesh";

	MeshPtr pMesh = MeshManager::getSingleton().load(meshName,
        ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,    
        HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY, 
		HardwareBuffer::HBU_STATIC_WRITE_ONLY, 
		true, true); //so we can still read it
    // Build tangent vectors, all our meshes use only 1 texture coordset 
	// Note we can build into VES_TANGENT now (SM2+)
    unsigned short src, dest;
    if (!pMesh->suggestTangentVectorBuildParams(VES_TANGENT, src, dest))
    {
        pMesh->buildTangentVectors(VES_TANGENT, src, dest);
    }

		Plane plane;
		plane.normal = Vector3::UNIT_Y;
		plane.d = 0;
		MeshManager::getSingleton().createPlane("Myplane",
			ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane,
			14500,14500,10,10,true,1,50,50,Vector3::UNIT_Z);
		Entity* pPlaneEnt = sceneMgr->createEntity( "plane", "Myplane" );
		
		//pPlaneEnt->setMaterialName("floorBumped");
		pPlaneEnt->setMaterialName("planeMat");
		
		pPlaneEnt->setCastShadows(false);
		sceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(pPlaneEnt);

		//sceneMgr->setShadowTechnique(Ogre::ShadowTechnique::SHADOWTYPE_STENCIL_MODULATIVE);

  // Create entity
	Ogre::Entity *mEntity;
    mEntity = sceneMgr->createEntity("Ent", meshName);
	//mEntity->setMaterialName("rockBumped");
	mEntity->setMaterialName("Examples/OffsetMapping/Specular");
	/*
	Vector4 vColour = Vector4(1.4,0.1,1,1);
	Vector4 vRadius = Vector4(1,0,0,0);
	mEntity->getSubEntity(0)->setCustomParameter(0,vColour);
	mEntity->getSubEntity(0)->setCustomParameter(1,vRadius);
	mEntity->setMaterialName("shader/gradient");
	*/

	//mEntity->setNormaliseNormals(true);
	
    SceneNode *node1 = sceneMgr->getRootSceneNode()->createChildSceneNode( "ogreNode" );
    node1->attachObject( mEntity );
	node1->setPosition(Vector3(0,0,-75));
	node1->setScale(Vector3(0.8,0.8,0.8));
	
}

void updateStats()
{
    static String currFps = "Current FPS: ";
    static String avgFps = "Average FPS: ";
    static String bestFps = "Best FPS: ";
    static String worstFps = "Worst FPS: ";
    static String tris = "Triangle Count: ";

    // update stats when necessary
    OverlayElement* guiAvg = OverlayManager::getSingleton().getOverlayElement("Core/AverageFps");
    OverlayElement* guiCurr = OverlayManager::getSingleton().getOverlayElement("Core/CurrFps");
    OverlayElement* guiBest = OverlayManager::getSingleton().getOverlayElement("Core/BestFps");
    OverlayElement* guiWorst = OverlayManager::getSingleton().getOverlayElement("Core/WorstFps");
    
    guiAvg->setCaption(avgFps + StringConverter::toString(mWindow->getAverageFPS()));
    guiCurr->setCaption(currFps + StringConverter::toString(mWindow->getLastFPS()));
    guiBest->setCaption(bestFps + StringConverter::toString(mWindow->getBestFPS())
        +" "+StringConverter::toString(mWindow->getBestFrameTime())+" ms");
    guiWorst->setCaption(worstFps + StringConverter::toString(mWindow->getWorstFPS())
        +" "+StringConverter::toString(mWindow->getWorstFrameTime())+" ms");
        
    OverlayElement* guiTris = OverlayManager::getSingleton().getOverlayElement("Core/NumTris");
    guiTris->setCaption(tris + StringConverter::toString(mWindow->getTriangleCount()));

    //OverlayElement* guiDbg = OverlayManager::getSingleton().getOverlayElement("Core/DebugText");
    //guiDbg->setCaption(mDebugText);
}

/*
void m() {
	using boost::get;

	// build property maps using associative_property_map
	std::map<std::string, int> name2age;
	std::map<std::string, double> name2gpa;
	boost::associative_property_map< std::map<std::string, int> >
		age_map(name2age);
	boost::associative_property_map< std::map<std::string, double> >
		gpa_map(name2gpa);

	std::string fred("Fred");
	// add key-value information
	name2age.insert(make_pair(fred,17));
	name2gpa.insert(make_pair(fred,2.7));

	// build and populate dynamic interface
	boost::dynamic_properties properties;
	properties.property("age",age_map);
	properties.property("gpa",gpa_map);

	manipulate_freds_info(properties);

	std::cout << "Fred's age: " << get(age_map,fred) << "\n"
		<< "Fred's gpa: " << get(gpa_map,fred) << "\n"; 

}
*/

class GameSystemParameter
{
public:
	
	template< typename T >
	GameSystemParameter(std::string name, shared_ptr<T> a ) {
		mObj = a;
		mName = name;
	}

	~GameSystemParameter() {	}

	template< typename T >
	bool setParam(std::string name, shared_ptr<T> a ) //Stack< T >::
	{
		mObj = a;
		mName = name;
		return true;
	}

	const std::string getName() {
		return mName;
	}
	
	bool setVal( std::string val )
	{
		if (shared_ptr<string>* sp = any_cast<shared_ptr<string>>(&mObj)) {
			**sp = val;
			return true;
		}
		if (shared_ptr<float>* sp = any_cast<shared_ptr<float>>(&mObj)) {
			**sp = StringConverter::parseReal(val);
			return true;
		}
		return false;
	}

	const std::string toString() {
		if( mObj.type() == typeid(shared_ptr<string>) ) {
			if (shared_ptr<string>* s = any_cast<shared_ptr<string>>(&mObj))
				return **s;
		}
		
		if( mObj.type() == typeid(shared_ptr<float>) ) {
			if (shared_ptr<float>* s = any_cast<shared_ptr<float>>(&mObj))
				return StringConverter::toString(**s);
		}
		if( mObj.type() == typeid(shared_ptr<int>) ) {
			if (shared_ptr<int>* s = any_cast<shared_ptr<int>>(&mObj))
				return StringConverter::toString(**s);
		}		
		if( mObj.type() == typeid(shared_ptr<unsigned int>) ) {
			if (shared_ptr<unsigned int>* s = any_cast<shared_ptr<unsigned int>>(&mObj))
				return StringConverter::toString(**s);
		}
		return NULL;
	}

	const float toFloat() {
		return StringConverter::parseReal(toString());
	}
	const int toInt() {
		return StringConverter::parseInt(toString());
	}
	const unsigned int toUInt() {
		return StringConverter::parseUnsignedInt(toString());
	}
	//const  toFloat() {
	//	return StringConverter::parseReal(toString());
	//}

private:
	boost::any mObj;
	
	std::string mName;
};

class GameSystem {
public:
	GameSystem(std::string name) { mName = name; }

	void registerParam(GameSystemParameter* param) {
		mParams[param->getName()] = param;
	}
	
	GameSystemParameter* getParam(std::string param) { 
		ParamList::iterator iter = mParams.begin();
		iter = mParams.find(param);

		if(iter != mParams.end()) 
			return iter->second;

		return NULL;
	}
	
	std::string getName() { 
		return mName; 
	}

private:
	typedef std::map<std::string,GameSystemParameter*> ParamList;
	ParamList mParams;

	std::string mName;
};

void PrintIfString(const any& Any) {
	if (const shared_ptr<string>* s =
		any_cast<shared_ptr<string> >(&Any)) {
			cout << **s << endl;
	}
	if (const shared_ptr<int>* i =
		any_cast<shared_ptr<int> >(&Any)) {
			cout << **i << endl;
	}
}

int main(int, char**) {

	std::map<std::string,boost::any> Stuff;


	//shared_ptr<int> sp;

	//sp = new int(1);	


	shared_ptr<string> SharedString1 (new string("string 1"));

	//SharedString1 = new string("string 1");

	//shared_ptr<float> SharedInt1 (new float(42.1f));	
	shared_ptr<float> SharedInt1;
	SharedInt1.reset(new float(42.1f));

	GameSystem *gs = new GameSystem("lala");
	
	gs->registerParam(new GameSystemParameter("param1",SharedString1));
	gs->registerParam(new GameSystemParameter("param2",SharedInt1));

	//std::string s = gs->getParam("param1")->getName();
	//gs->getParam("param1")->setVal("1");
	//gs->getParam("param2")->setVal("1.1f");
	
	std::string s = gs->getParam("param1")->toString();
	float f = gs->getParam("param2")->toFloat();

	*SharedString1 = "b";
	*SharedInt1 = 2.2f;
	
	(*SharedInt1)++;

	*SharedInt1 = *SharedInt1 + 1;

	s = gs->getParam("param1")->toString();
	f = gs->getParam("param2")->toFloat();

	gs->getParam("param1")->setVal("caca");
	s = gs->getParam("param1")->toString();

	//float res = gs->getParam("param2")->toFloat();

	//Stuff. .push_back(SharedString1);
	//Stuff.push_back(SharedInt1);

	Stuff["1"] = SharedString1;
	Stuff["2"] = SharedInt1;
	
	for_each(Stuff.begin(), Stuff.end(),
		PrintIfString);

	*SharedInt1 = 32;

	std::map<std::string,boost::any>::iterator iter = Stuff.begin();
	iter = Stuff.find("2");
	if(iter != Stuff.end()) {
		const shared_ptr<string>* s = any_cast<shared_ptr<string> >(&iter->second);
		cout << **s << endl;
	}

		//std::string a = iter->second;

/*
	for_each(Stuff.begin(), Stuff.end(),
		PrintIfString);
*/

	std::vector<boost::any> Stuff2;

	//std::list<boost::any> values;

	//int *i = new int(0);
	//*i = 1;
	//*i = 1;
	//int i;
	//i = 1;

	//boost::any a; // = new boost::any(i);
	//a = boost::any(i);

	//boost::any *a; // = new boost::any(boost::any(i));
	//a = *(boost::any(i));

	//values.push_back(a);
	//*i = 2;
	
	//std::list<boost::any>::const_iterator it;
	//for(it=values.begin(); it!=values.end(); ++it)
	//{
	//	int pi = boost::any_cast <int>(&it);
	//	cout << pi << endl; // each element on a separate line
		//cout << boost::any_cast<int>(*it) << endl; // each element on a separate line
	//} 
	
	//Testcl *tc = new Testcl();

	
	//std::string a;
	//a = "asdasd";
	//Ogre::Any *any1 = new Any(a);
	//tc->setParam(any1);

	//int *i = new int(0); // = new int; // = new int;
	//*i = 134;

	//Ogre::Any *any2 = new Any(i);
	//tc->setParam(any2);

	//int *r;
	//*r = *(tc->toInt());
	//cout << "1 " << *r << "\n";

	//*i = 432;
	//Ogre::Any *any3 = new Any(i);
	//tc->setVal(any3);
	//r = tc->toInt();
	//cout << "2 " << *r << "\n";

	//any2->swap(&Ogre::Any("666")); //Ogre::Any(666);
	//*any2 = Ogre::Any(1);
	//r = tc->toInt();
	//cout << "3 " << *r << "\n";

	//std::string r = tc->toString();



	/**********************************************************************************************************/

	//GameSystem *gs = new GameSystem("lala");
	
	//int *test;
	//*test = 1;

	//Stack<int> *test;
	//test->setVal(1);
	//gs->registerParam<int>(test,"caca");
	
	//Ogre::Any()
	
	//Stack<int> doubleStack;
	//int *parameter = new int();
	//*parameter = 1;
	
	//cout << "Pointer Original " << *parameter << "\n";
	
	//doubleStack.setParam(parameter);
	//doubleStack.setVal(2);
	//int i2 = doubleStack.getVal();
	//std::string rs = doubleStack.toString();

	//cout << "From Stack " << doubleStack.getVal() << "\n";

	/**********************************************************************************************************/


	float elapsed = 0.0f;

	mRoot = new Root();

	init();
	addResources();
    setupScene();
	initInput();
	fillScene();

    // this keeps track of time (seconds to keep it easy)
		static Ogre::Timer timer;
    float currentTime = float(timer.getMilliseconds()) * 0.001;

	Overlay* o = OverlayManager::getSingleton().getByName("Core/DebugOverlay");
	if(o)
		o->show();

    while (running) {
        Ogre::WindowEventUtilities::messagePump();

        float deltaTime = (float(timer.getMilliseconds()) * 0.001) - currentTime;
        currentTime += deltaTime;

        captureInput();

        if (keys->isKeyDown(OIS::KC_ESCAPE))
            running = false;
		
		if (keys->isKeyDown(OIS::KC_R) && float(timer.getMilliseconds()) > elapsed) {
			elapsed = float(timer.getMilliseconds()) + 300.0f;
			if(cam->getPolygonMode() == Ogre::PM_SOLID)
				cam->setPolygonMode(Ogre::PM_WIREFRAME);
			else
				cam->setPolygonMode(Ogre::PM_SOLID);
		}
		
		if(keys->isKeyDown(OIS::KC_LEFT))
			lightNode->setPosition(lightNode->getPosition().x-0.2f,lightNode->getPosition().y,lightNode->getPosition().z);
		if(keys->isKeyDown(OIS::KC_RIGHT))
			lightNode->setPosition(lightNode->getPosition().x+0.2f,lightNode->getPosition().y,lightNode->getPosition().z);
		if(keys->isKeyDown(OIS::KC_UP))
			lightNode->setPosition(lightNode->getPosition().x,lightNode->getPosition().y+0.2,lightNode->getPosition().z);
		if(keys->isKeyDown(OIS::KC_DOWN))
			lightNode->setPosition(lightNode->getPosition().x,lightNode->getPosition().y-0.2,lightNode->getPosition().z);

		if(keys->isKeyDown(OIS::KC_F1)) {
			if(o->isVisible())
				o->hide();
			else
				o->show();
		}

        handleCamera(deltaTime);

		updateStats();

        mRoot->renderOneFrame();
    }

    stopInput();
    stopRenderer();
}
